/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.weather.myframe;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.os.Build;
import android.util.TypedValue;
import android.view.View;

/*
 * Modified from version in AOSP.
 *
 * This class is used to display a highlighted cropping rectangle
 * overlayed on the image. There are two coordinate spaces in use. One is
 * image, another is screen. computeLayout() uses matrix to map from image
 * space to screen space.
 */
class HighlightView {

	public static final int GROW_NONE = (1 << 0);
	public static final int GROW_LEFT_EDGE = (1 << 1);
	public static final int GROW_RIGHT_EDGE = (1 << 2);
	public static final int GROW_TOP_EDGE = (1 << 3);
	public static final int GROW_BOTTOM_EDGE = (1 << 4);
	public static final int MOVE = (1 << 5);

	private static final int DEFAULT_HIGHLIGHT_COLOR = 0xFF33B5E5;
	private static final float HANDLE_RADIUS_DP = 12f;
	private static final float OUTLINE_DP = 2f;

	enum ModifyMode {
		None, Move, Grow
	}

	enum HandleMode {
		Changing, Always, Never
	}

	RectF cropRect; // Image space
	Rect drawRect; // Screen space
	Matrix matrix;
	private RectF imageRect; // Image space

	private final Paint outsidePaint = new Paint();
	private final Paint outlinePaint = new Paint();
	private final Paint handlePaint = new Paint();

	private View viewContext; // View displaying image
	private boolean showThirds;
	private int highlightColor;

	private ModifyMode modifyMode = ModifyMode.None;
	private HandleMode handleMode = HandleMode.Changing;
	private boolean maintainAspectRatio;
	private float initialAspectRatio;
	private float handleRadius;
	private float outlineWidth;
	private boolean isFocused;

	public HighlightView(View context) {
		viewContext = context;
		initStyles(context.getContext());
	}

	private void initStyles(Context context) {
		TypedValue outValue = new TypedValue();
		context.getTheme().resolveAttribute(R.attr.cropImageStyle, outValue, true);
		TypedArray attributes = context.obtainStyledAttributes(outValue.resourceId, R.styleable.CropImageView);
		try {
			showThirds = attributes.getBoolean(R.styleable.CropImageView_showThirds, false);
			highlightColor = attributes.getColor(R.styleable.CropImageView_highlightColor, DEFAULT_HIGHLIGHT_COLOR);
			handleMode = HandleMode.values()[attributes.getInt(R.styleable.CropImageView_showHandles, 0)];
		} finally {
			attributes.recycle();
		}
	}

	public void setup(Matrix m, Rect imageRect, RectF cropRect, boolean maintainAspectRatio) {
		matrix = new Matrix(m);

		this.cropRect = cropRect;
		this.imageRect = new RectF(imageRect);
		this.maintainAspectRatio = maintainAspectRatio;

		initialAspectRatio = this.cropRect.width() / this.cropRect.height();
		drawRect = computeLayout();

		outsidePaint.setARGB(125, 50, 50, 50);
		outlinePaint.setStyle(Paint.Style.STROKE);
		outlinePaint.setAntiAlias(true);
		outlineWidth = dpToPx(OUTLINE_DP);

		handlePaint.setColor(highlightColor);
		handlePaint.setStyle(Paint.Style.FILL);
		handlePaint.setAntiAlias(true);
		handleRadius = dpToPx(HANDLE_RADIUS_DP);

		modifyMode = ModifyMode.None;
	}

	private float dpToPx(float dp) {
		return dp * viewContext.getResources().getDisplayMetrics().density;
	}

	protected void draw(Canvas canvas) {
		canvas.save();
		Path path = new Path();
		outlinePaint.setStrokeWidth(outlineWidth);
		if (!hasFocus()) {
			outlinePaint.setColor(Color.BLACK);
			canvas.drawRect(drawRect, outlinePaint);
		} else {
			Rect viewDrawingRect = new Rect();
			viewContext.getDrawingRect(viewDrawingRect);

			path.addRect(new RectF(drawRect), Path.Direction.CW);
			outlinePaint.setColor(highlightColor);

			if (isClipPathSupported(canvas)) {
				canvas.clipPath(path, Region.Op.DIFFERENCE);
				canvas.drawRect(viewDrawingRect, outsidePaint);
			} else {
				drawOutsideFallback(canvas);
			}

			canvas.restore();
			canvas.drawPath(path, outlinePaint);

			if (showThirds) {
				drawThirds(canvas);
			}

			if (handleMode == HandleMode.Always || (handleMode == HandleMode.Changing && modifyMode == ModifyMode.Grow)) {
				drawHandles(canvas);
			}
		}
	}

	/*
	 * Fall back to naive method for darkening outside crop area
	 */
	private void drawOutsideFallback(Canvas canvas) {
		canvas.drawRect(0, 0, canvas.getWidth(), drawRect.top, outsidePaint);
		canvas.drawRect(0, drawRect.bottom, canvas.getWidth(), canvas.getHeight(), outsidePaint);
		canvas.drawRect(0, drawRect.top, drawRect.left, drawRect.bottom, outsidePaint);
		canvas.drawRect(drawRect.right, drawRect.top, canvas.getWidth(), drawRect.bottom, outsidePaint);
	}

	/*
	 * Clip path is broken, unreliable or not supported on: - JellyBean MR1 - ICS & ICS MR1 with hardware acceleration turned on
	 */
	@SuppressLint("NewApi")
	private boolean isClipPathSupported(Canvas canvas) {
		if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1) {
			return false;
		} else if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) || Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
			return true;
		} else {
			return !canvas.isHardwareAccelerated();
		}
	}

	private void drawHandles(Canvas canvas) {
		int xMiddle = drawRect.left + ((drawRect.right - drawRect.left) / 2);
		int yMiddle = drawRect.top + ((drawRect.bottom - drawRect.top) / 2);

		canvas.drawCircle(drawRect.left, yMiddle, handleRadius, handlePaint);
		canvas.drawCircle(xMiddle, drawRect.top, handleRadius, handlePaint);
		canvas.drawCircle(drawRect.right, yMiddle, handleRadius, handlePaint);
		canvas.drawCircle(xMiddle, drawRect.bottom, handleRadius, handlePaint);
	}

	private void drawThirds(Canvas canvas) {
		outlinePaint.setStrokeWidth(1);
		float xThird = (drawRect.right - drawRect.left) / 3;
		float yThird = (drawRect.bottom - drawRect.top) / 3;

		canvas.drawLine(drawRect.left + xThird, drawRect.top, drawRect.left + xThird, drawRect.bottom, outlinePaint);
		canvas.drawLine(drawRect.left + xThird * 2, drawRect.top, drawRect.left + xThird * 2, drawRect.bottom, outlinePaint);
		canvas.drawLine(drawRect.left, drawRect.top + yThird, drawRect.right, drawRect.top + yThird, outlinePaint);
		canvas.drawLine(drawRect.left, drawRect.top + yThird * 2, drawRect.right, drawRect.top + yThird * 2, outlinePaint);
	}

	public void setMode(ModifyMode mode) {
		if (mode != modifyMode) {
			modifyMode = mode;
			viewContext.invalidate();
		}
	}

	// Determines which edges are hit by touching at (x, y)
	public int getHit(float x, float y) {
		Rect r = computeLayout();
		final float hysteresis = 20F;
		int retval = GROW_NONE;

		// verticalCheck makes sure the position is between the top and
		// the bottom edge (with some tolerance). Similar for horizCheck.
		boolean verticalCheck = (y >= r.top - hysteresis) && (y < r.bottom + hysteresis);
		boolean horizCheck = (x >= r.left - hysteresis) && (x < r.right + hysteresis);

		// Check whether the position is near some edge(s)
		if ((Math.abs(r.left - x) < hysteresis) && verticalCheck) {
			retval |= GROW_LEFT_EDGE;
		}
		if ((Math.abs(r.right - x) < hysteresis) && verticalCheck) {
			retval |= GROW_RIGHT_EDGE;
		}
		if ((Math.abs(r.top - y) < hysteresis) && horizCheck) {
			retval |= GROW_TOP_EDGE;
		}
		if ((Math.abs(r.bottom - y) < hysteresis) && horizCheck) {
			retval |= GROW_BOTTOM_EDGE;
		}

		// Not near any edge but inside the rectangle: move
		if (retval == GROW_NONE && r.contains((int) x, (int) y)) {
			retval = MOVE;
		}
		return retval;
	}

	// Handles motion (dx, dy) in screen space.
	// The "edge" parameter specifies which edges the user is dragging.
	void handleMotion(int edge, float dx, float dy) {
		Rect r = computeLayout();
		if (edge == MOVE) {
			// Convert to image space before sending to moveBy()
			moveBy(dx * (cropRect.width() / r.width()), dy * (cropRect.height() / r.height()));
		} else {
			if (((GROW_LEFT_EDGE | GROW_RIGHT_EDGE) & edge) == 0) {
				dx = 0;
			}

			if (((GROW_TOP_EDGE | GROW_BOTTOM_EDGE) & edge) == 0) {
				dy = 0;
			}

			// Convert to image space before sending to growBy()
			float xDelta = dx * (cropRect.width() / r.width());
			float yDelta = dy * (cropRect.height() / r.height());
			growBy((((edge & GROW_LEFT_EDGE) != 0) ? -1 : 1) * xDelta, (((edge & GROW_TOP_EDGE) != 0) ? -1 : 1) * yDelta);
		}
	}

	// Grows the cropping rectangle by (dx, dy) in image space
	void moveBy(float dx, float dy) {
		Rect invalRect = new Rect(drawRect);

		cropRect.offset(dx, dy);

		// Put the cropping rectangle inside image rectangle
		cropRect.offset(Math.max(0, imageRect.left - cropRect.left), Math.max(0, imageRect.top - cropRect.top));

		cropRect.offset(Math.min(0, imageRect.right - cropRect.right), Math.min(0, imageRect.bottom - cropRect.bottom));

		drawRect = computeLayout();
		invalRect.union(drawRect);
		invalRect.inset(-(int) handleRadius, -(int) handleRadius);
		viewContext.invalidate(invalRect);
	}

	// Grows the cropping rectangle by (dx, dy) in image space.
	void growBy(float dx, float dy) {
		if (maintainAspectRatio) {
			if (dx != 0) {
				dy = dx / initialAspectRatio;
			} else if (dy != 0) {
				dx = dy * initialAspectRatio;
			}
		}

		// Don't let the cropping rectangle grow too fast.
		// Grow at most half of the difference between the image rectangle and
		// the cropping rectangle.
		RectF r = new RectF(cropRect);
		if (dx > 0F && r.width() + 2 * dx > imageRect.width()) {
			dx = (imageRect.width() - r.width()) / 2F;
			if (maintainAspectRatio) {
				dy = dx / initialAspectRatio;
			}
		}
		if (dy > 0F && r.height() + 2 * dy > imageRect.height()) {
			dy = (imageRect.height() - r.height()) / 2F;
			if (maintainAspectRatio) {
				dx = dy * initialAspectRatio;
			}
		}

		r.inset(-dx, -dy);

		// Don't let the cropping rectangle shrink too fast
		final float widthCap = 25F;
		if (r.width() < widthCap) {
			r.inset(-(widthCap - r.width()) / 2F, 0F);
		}
		float heightCap = maintainAspectRatio ? (widthCap / initialAspectRatio) : widthCap;
		if (r.height() < heightCap) {
			r.inset(0F, -(heightCap - r.height()) / 2F);
		}

		// Put the cropping rectangle inside the image rectangle
		if (r.left < imageRect.left) {
			r.offset(imageRect.left - r.left, 0F);
		} else if (r.right > imageRect.right) {
			r.offset(-(r.right - imageRect.right), 0F);
		}
		if (r.top < imageRect.top) {
			r.offset(0F, imageRect.top - r.top);
		} else if (r.bottom > imageRect.bottom) {
			r.offset(0F, -(r.bottom - imageRect.bottom));
		}

		cropRect.set(r);
		drawRect = computeLayout();
		viewContext.invalidate();
	}

	// Returns the cropping rectangle in image space with specified scale
	public Rect getScaledCropRect(float scale) {
		return new Rect((int) (cropRect.left * scale), (int) (cropRect.top * scale), (int) (cropRect.right * scale), (int) (cropRect.bottom * scale));
	}

	// Maps the cropping rectangle from image space to screen space
	private Rect computeLayout() {
		RectF r = new RectF(cropRect.left, cropRect.top, cropRect.right, cropRect.bottom);
		matrix.mapRect(r);
		return new Rect(Math.round(r.left), Math.round(r.top), Math.round(r.right), Math.round(r.bottom));
	}

	public void invalidate() {
		drawRect = computeLayout();
	}

	public boolean hasFocus() {
		return isFocused;
	}

	public void setFocus(boolean isFocused) {
		this.isFocused = isFocused;
	}

}
